/********************************************************************************
* @author: Ternura_xiang
* @email: 2022101883@nit.edu.cn
* @date: Jul 26, 2024
* @version: 1.0
* @description: ILI9488屏幕驱动代码
********************************************************************************/

#include "ILI9488.h"
#include "font.h"

/*
 * 一点视频RAM来加快速度。这使 HAL SPI 库可以批量传输数据，而不是单独的事务。
 * 最小值为 3（R、G 和 B 各 1 个字节）。
 * 理论最大值为 0xFFFF - 1，但这似乎行不通。
 * 选择适合您的 RAM 预算并与您的控制器配合使用的大小。
 */
#define V_BUFFER_SIZE 0x3FFF
uint8_t v_buffer[V_BUFFER_SIZE];
uint16_t buffer_counter = 0;


/**
 * @brief 将 V-RAM 缓冲区写入显示器。
 * TODO: currently only uses SPI.
 */
void write_buffer() {
    LCD_SPI_Transmit(&LCD_SPI, (uint8_t *) &v_buffer, buffer_counter, 10);
    buffer_counter = 0;
}

/**
 * @brief 重启LCD
 * @retval None
 */
void LCD_Reset()
{
    LCD_RES_RESET;
    LCD_Delay(100);
    LCD_RES_SET;
    LCD_Delay(50);
}

/**
 * @brief 发送命令
 * @note 使用软件SPI时需重写此函数
 * @param command: 需要发送的指令
 * @retval None
 */
void LCD_WriteCommand(uint8_t command)
{
    // DC拉低，命令模式
    LCD_DC_RESET;
    // CS拉低，片选
    LCD_CS_RESET;
    LCD_SPI_Transmit(&LCD_SPI, &command, 1, 1000);
    LCD_CS_SET;
}

/**
 * @brief 发送数据
 * @note 使用软件SPI时需重写此函数
 * @param data: 需要发送的数据
 * @retval None
 */
void LCD_WriteData(uint8_t data)
{
    // DC拉低，数据模式
    LCD_DC_SET;
    // CS拉低，片选
    LCD_CS_RESET;
    LCD_SPI_Transmit(&LCD_SPI, &data, 1, 1000);
    LCD_CS_SET;
}

/**
 * @brief 发送16bit数据
 * @note 使用软件SPI时需重写此函数
 * @param data: 需要发送的数据
 * @retval None
 */
void LCD_WriteData_u16(uint16_t data)
{
    LCD_WriteData(data >> 8);
    LCD_WriteData(data & 0x0F);
}

/**
 * @brief 交换两个8bit数据
 */
void swap_8bit(char *num1, char *num2) {
    char temp = *num2;
    *num2 = *num1;
    *num1 = temp;
}

/**
 * @brief 交换两个16bit数据
 */
void swap_16bit(uint16_t *num1, uint16_t *num2) {
    int temp = *num2;
    *num2 = *num1;
    *num1 = temp;
}


/**
 * @brief LCD初始化
 * @retval None
 */
void LCD_Init()
{
    LCD_Reset();

    LCD_WriteCommand(LCD_AdjustCTRL3);
    LCD_WriteData(0xA9);
    LCD_WriteData(0x51);
    LCD_WriteData(0x2C);
    LCD_WriteData(0x82);
    LCD_WriteCommand(LCD_PWRCTRL1);
    LCD_WriteData(0x11);
    LCD_WriteData(0x09);
    LCD_WriteCommand(LCD_PWRCTRL2);
    LCD_WriteData(0x41);
    LCD_WriteCommand(LCD_VCOMCTRL);
    LCD_WriteData(0x00);
    LCD_WriteData(0x0A);
    LCD_WriteData(0x80);
    LCD_WriteCommand(LCD_FRMCTR1);
    LCD_WriteData(0xB0);
    LCD_WriteData(0x11);
    LCD_WriteCommand(LCD_INVTR);
    LCD_WriteData(0x02);
    LCD_WriteCommand(LCD_DISCTRL);
    LCD_WriteData(0x02);
    LCD_WriteData(0x42);
    LCD_WriteCommand(LCD_ETMOD);
    LCD_WriteData(0xc6);
    LCD_WriteCommand(LCD_HSLCTRL);
    LCD_WriteData(0x00);
    LCD_WriteData(0x04);
    LCD_WriteCommand(LCD_SETIMAGE);     // 设置图像功能
    LCD_WriteData(0x00);
    LCD_WriteCommand(LCD_MADCTL);
    LCD_WriteData((LCD_MY<<7)|(LCD_MX<<6)|(LCD_MV<<5)|(LCD_ML<<4)|(LCD_BGR<<3)|(LCD_MH<<2));
    LCD_WriteCommand(LCD_COLMOD);
    LCD_WriteData(0x66);
    LCD_WriteCommand(LCD_PGAMCTRL);
    LCD_WriteData(0x00);
    LCD_WriteData(0x07);
    LCD_WriteData(0x10);
    LCD_WriteData(0x09);
    LCD_WriteData(0x17);
    LCD_WriteData(0x0B);
    LCD_WriteData(0x41);
    LCD_WriteData(0x89);
    LCD_WriteData(0x4B);
    LCD_WriteData(0x0A);
    LCD_WriteData(0x0C);
    LCD_WriteData(0x0E);
    LCD_WriteData(0x18);
    LCD_WriteData(0x1B);
    LCD_WriteData(0x0F);
    LCD_WriteCommand(LCD_NGAMCTRL);
    LCD_WriteData(0x00);
    LCD_WriteData(0x17);
    LCD_WriteData(0x1A);
    LCD_WriteData(0x04);
    LCD_WriteData(0x0E);
    LCD_WriteData(0x06);
    LCD_WriteData(0x2F);
    LCD_WriteData(0x45);
    LCD_WriteData(0x43);
    LCD_WriteData(0x02);
    LCD_WriteData(0x0A);
    LCD_WriteData(0x09);
    LCD_WriteData(0x32);
    LCD_WriteData(0x36);
    LCD_WriteData(0x0F);
    LCD_WriteCommand(LCD_SLPOUT);
    LCD_Delay(120);
    LCD_WriteCommand(LCD_DISON);
}

/**
 * @brief 写入屏幕地址函数
 * @param x_start:  起始x坐标
 * @param y_start:  起始y坐标
 * @param x_end:    终点x坐标
 * @param y_end:    终点y坐标
 * @retval None
 */
void LCD_SetWindow(uint16_t x_start,uint16_t y_start,uint16_t x_end,uint16_t y_end)
{
    // 检查值是否按顺序排列
    if(x_end < x_start)
        swap_16bit(&x_end, &x_start);
    if(y_end < y_start)
        swap_16bit(&y_end, &y_start);

    LCD_WriteCommand(LCD_CASET);
    LCD_WriteData(x_start >> 8);
    LCD_WriteData(x_start & 0x00FF);

    LCD_WriteData(x_end >> 8);
    LCD_WriteData(x_end & 0x00FF);

    LCD_WriteCommand(LCD_PASET);
    LCD_WriteData(y_start >> 8);
    LCD_WriteData(y_start & 0x00FF);

    LCD_WriteData(y_end >> 8);
    LCD_WriteData(y_end & 0x00FF);

    LCD_WriteCommand(LCD_RAMWR);
}

/**
 * @brief 将RGB(R,G,B)转化为RGB565
 * @param red:      红色
 * @param green:    绿色
 * @param blue:     蓝色
 * @retval 转化后的16位RGB565
 */
uint16_t RGB565(uint8_t red, uint8_t green, uint8_t blue)
{
    uint16_t color = 0;
    color |= (red >> 3) << 11;      //将red的高5位放到color的高5位
    color |= (green >> 2) << 5;     //将green的高6位放到color的中间6位
    color |= blue >> 3;             //将blue的高5位放到color的低5位
    return color;
}

/**
 * @brief 区域颜色填充
 * @param x_start:  起始x坐标
 * @param y_start:  起始y坐标
 * @param x_end:    终点x坐标
 * @param y_end:    终点y坐标
 * @param color:    填充颜色
 * @retval None
 */
void LCD_FullArea(uint16_t x_start, uint16_t y_start, uint16_t x_end, uint16_t y_end, uint16_t color)
{
    //All my colours are in 16-bit RGB 5-6-5 so they have to be converted to 18-bit RGB
    uint8_t R = (color >> 8) & 0xF8;
    uint8_t G = (color >> 3) & 0xFC;
    uint8_t B = (color << 3);

    // 设置绘图区域
    LCD_SetWindow(x_start, y_start, x_end, y_end);

    // 为了提高速度，我们将在此处手动执行 SPI 写入
    // 数据表说 CS 在数据部分之间是否发生变化并不重要，但我不相信它。
    // CS 低开始数据
    LCD_DC_SET;
    LCD_CS_RESET;

    // 为每个像素写入颜色
    for(int y = 0; y < y_end - y_start + 1 ; y++) {
        for(int x = 0; x < x_end - x_start + 1; x++) {
            v_buffer[buffer_counter] = R;
            buffer_counter++;
            v_buffer[buffer_counter] = G;
            buffer_counter++;
            v_buffer[buffer_counter] = B;
            buffer_counter++;

            // 如果缓冲区已满，则将其发送到显示器
            if(buffer_counter > V_BUFFER_SIZE - 3) {
                write_buffer();
            }
        }
    }
    // 发送剩余的字节
    write_buffer();
    // 将 CS 恢复到高
    LCD_CS_SET;
}

/**
 * @brief 填充整块屏幕
 * @param color:    填充的颜色
 * @retval None
 */
void LCD_Full(uint16_t color)
{
    LCD_FullArea(0, 0, LCD_HEIGHT, LCD_WIDTH, color);
}

/**
 * @brief 画点
 * @param x:        x坐标
 * @param y:        y坐标
 * @param color:    颜色
 * @retval None
 */
void LCD_DrawPixel(uint16_t x, uint16_t y, uint16_t color)
{
    uint8_t R = (color >> 8) & 0xF8;
    uint8_t G = (color >> 3) & 0xFC;
    uint8_t B = (color << 3);

    LCD_SetWindow(x, y, x+1, y+1);
    LCD_WriteData(R);
    LCD_WriteData(G);
    LCD_WriteData(B);
}

/**
 * 画一个指定大小的字符()
 * @param x:    x坐标
 * @param y:    y坐标
 * @param c:    字符(支持ASCII 32-126)
 * @param color: 颜色
 * @param size: 字符的大小(倍)
 */
void LCD_DrawChar(uint16_t x, uint16_t y, char c, uint16_t color, uint8_t size)
{
    char line;
    unsigned int font_index = (c - 32);

    // 从字体文件中获取像素行。
    for(uint16_t i=0; i<13; i++ ) {
        line = ASCII[font_index][12 - i];

        // 绘制像素到屏幕
        for(uint16_t j=0; j<8; j++) {
            if(line & (0x01 << j)) {
                LCD_FullArea(x+((8-j)*size), y+((i)*size), x+((8-j)*size)+size, y+((i)*size)+size, color);
            }
        }
    }
}

/**
 * 显示字符串
 * @param x:    x坐标
 * @param y:    y坐标
 * @param color: 颜色
 * @param size: 字符的大小(倍)
 * @param str:  字符串
 */
void LCD_DrawStr(uint16_t x, uint16_t y, char *str, uint16_t color, uint8_t size)
{
    int char_width = size * 9;  // 计算出每个字符的大小

    // 遍历字符串中的每个字符
    int counter = 0;
    while(str[counter] != '\0') {
        int char_pos = x + (counter * char_width);  // 计算字符位置
        LCD_DrawChar(char_pos, y, str[counter], color, size);   // 将字符写入显示
        counter++;  // 下一个字符
    }
}